#include "common.h"
#include "cpu/reg.h"
#include "memory/tlb.h"
#include "../../lib-common/x86-inc/mmu.h"

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);

/* Memory accessing interfaces */


lnaddr_t segment_translate(swaddr_t addr, size_t len, uint8_t sreg) {
	if(!cpu.cr0.protect_enable) return addr;
	else {
		return cpu.reg[sreg].base + addr;
	}
}

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	return dram_read(addr, len) & (~0u >> ((4 - len) << 3));
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	dram_write(addr, len, data);
}

hwaddr_t page_translate(lnaddr_t addr) {
	if(cpu.cr0.protect_enable&& cpu.cr0.paging ) {
		uint32_t dir = addr >> 22 ;
		uint32_t page = ( addr >> 12 ) & 0x3ff;
		uint32_t offset = addr & 0xfff;
		//search the TLB
		int i = read_tlb(addr);
		if(i != TLB_SIZE) {
			hwaddr_t hwaddr = (tlb[i].page << 12) + offset;
			return hwaddr;
		}
		//get the page directory base
		uint32_t dir_base = cpu.cr3.page_directory_base;
		uint32_t dir_offset = ( dir_base << 12 ) + ( dir << 2 );
		PDE first;
  		first.val = hwaddr_read(dir_offset, 4);
		assert(first.present == 1);

		//get the page table base
		uint32_t page_base = first.page_frame;
		uint32_t page_offset = ( page_base << 12 ) + ( page << 2 );
		PTE second;
		second.val = hwaddr_read(page_offset, 4);
  		assert(second.present == 1);

		//get the page frame
		uint32_t frame_base = second.page_frame;
		hwaddr_t hwaddr = ( frame_base << 12 ) + offset;
		write_tlb(addr, hwaddr);
		return hwaddr;
	}
	else {
		return addr;
	}
}

hwaddr_t page_translate_additional(lnaddr_t addr, int* flag) {
	if(cpu.cr0.protect_enable&& cpu.cr0.paging ) {
		uint32_t dir = addr >> 22 ;
		uint32_t page = ( addr >> 12 ) & 0x3ff;
		uint32_t offset = addr & 0xfff;
		//get the page directory base
		uint32_t dir_base = cpu.cr3.page_directory_base;
		uint32_t dir_offset = ( dir_base << 12 ) + ( dir << 2 );
		PDE first;
  		first.val = hwaddr_read(dir_offset, 4);
		/* judge whether the page directory entry is present */
		if(first.present == 0) {
			*flag = 1;
			return 0;
		}

		//get the page table base
		uint32_t page_base = first.page_frame;
		uint32_t page_offset = ( page_base << 12 ) + ( page << 2 );
		PTE second;
		second.val = hwaddr_read(page_offset, 4);
		/* judge whether the page table entry is present */
  		if(second.present == 0) {
			*flag = 2;
			return 0;
		}

		//get the page frame
		uint32_t frame_base = second.page_frame;
		hwaddr_t hwaddr = ( frame_base << 12 ) + offset;
		return hwaddr;
	}
	else {
		return addr;
	}
}
uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	assert (len == 1 || len == 2 || len == 4);
	uint32_t offset =  addr & 0xfff;
	//judge whether the address is in the page
	if(offset + len - 1 > 0xfff) {
		size_t length = 0xfff - offset + 1;
		uint32_t low = lnaddr_read(addr, length);
		uint32_t high = lnaddr_read(addr + length, len - length);
		return low + (high << (length << 3));
	}
	else {
		hwaddr_t hwaddr = page_translate(addr);
		return hwaddr_read(hwaddr, len);
	}
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	assert (len == 1 || len == 2 || len == 4);
	uint32_t offset =  addr & 0xfff;
	//judge whether the address is in the page
	if(offset + len - 1 > 0xfff) {
		size_t length = 0xfff - offset + 1;
		lnaddr_write(addr, length, data & ((1 << (length << 3)) - 1));
		lnaddr_write(addr + length, len - length, data >> (length << 3));
	}
	else {
		hwaddr_t hwaddr = page_translate(addr);
		hwaddr_write(hwaddr, len, data);
	}
}

uint32_t swaddr_read(swaddr_t addr, size_t len, uint8_t sreg) {
	assert(len == 1 || len == 2 || len == 4);
	lnaddr_t lnaddr = segment_translate(addr, len, sreg);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data, uint8_t sreg) {
	assert(len == 1 || len == 2 || len == 4);
	lnaddr_t lnaddr = segment_translate(addr, len, sreg);
	lnaddr_write(lnaddr, len, data);
}



